#include "rendMaterial.hpp"

namespace rend
{

const std::string Material::sn_tex_normalizer_cubemap	= "normalizer";
const std::string Material::sn_tex_exponentiation_map	= "exponent";

////////////////////////////////////////////////////////////////////////
// individual matrices
////////////////////////////////////////////////////////////////////////

const std::string Material::sn_mat4_mvp					= "mvp";
const std::string Material::sn_mat4_mvp_inv				= "mvp_inv";
const std::string Material::sn_mat4_mv					= "mv";
const std::string Material::sn_mat4_mv_inv				= "mv_inv";
const std::string Material::sn_mat4_world				= "world";
const std::string Material::sn_mat4_world_inv			= "world_inv";
const std::string Material::sn_mat4_view				= "view";
const std::string Material::sn_mat4_view_inv			= "view_inv";
const std::string Material::sn_mat4_proj				= "proj";
const std::string Material::sn_mat4_proj_inv			= "proj_inv";

////////////////////////////////////////////////////////////////////////
// matrix arrays
////////////////////////////////////////////////////////////////////////

#if defined(GLSL_QUIRK_ARRAY_PRINCIPAL_ELEMENT)
const std::string Material::sn_mat4v_bone				= "bone[0]";
#else
const std::string Material::sn_mat4v_bone				= "bone";
#endif

////////////////////////////////////////////////////////////////////////
// individual vectors
////////////////////////////////////////////////////////////////////////

const std::string Material::sn_vec4_angular_time		= "angular_time";
const std::string Material::sn_vec4_vp_obj				= "vp_obj";

#if defined(GLSL_QUIRK_UNINDEXED_SOLE_ELEMENT)
const std::string Material::sn_vec4_lp_obj_0			= "lp_obj";
#else
const std::string Material::sn_vec4_lp_obj_0			= "lp_obj[0]";
#endif

const std::string Material::sn_vec4_lp_obj_1			= "lp_obj[1]";
const std::string Material::sn_vec4_lp_obj_2			= "lp_obj[2]";
const std::string Material::sn_vec4_lp_obj_3			= "lp_obj[3]";
const std::string Material::sn_vec4_lp_obj_4			= "lp_obj[4]";
const std::string Material::sn_vec4_lp_obj_5			= "lp_obj[5]";
const std::string Material::sn_vec4_lp_obj_6			= "lp_obj[6]";
const std::string Material::sn_vec4_lp_obj_7			= "lp_obj[7]";

////////////////////////////////////////////////////////////////////////
// vector arrays
////////////////////////////////////////////////////////////////////////

#if defined(GLSL_QUIRK_ARRAY_PRINCIPAL_ELEMENT)
const std::string Material::sn_vec4v_lp_obj				= "lp_obj[0]";
#else
const std::string Material::sn_vec4v_lp_obj				= "lp_obj";
#endif

////////////////////////////////////////////////////////////////////////
// default registers
////////////////////////////////////////////////////////////////////////

Material::Program::Register			Material::Program::s_default_register(256);
Material::LinkedProgram::Register	Material::LinkedProgram::s_default_register(256);
Material::Texture::Register			Material::Texture::s_default_register(256);
Material::EnvMat4::Register			Material::EnvMat4::s_default_register(32);
Material::EnvMat4v::Register		Material::EnvMat4v::s_default_register(8);
Material::EnvVec4::Register			Material::EnvVec4::s_default_register(32);
Material::EnvVec4v::Register		Material::EnvVec4v::s_default_register(8);


Material::Texture::Register* Material::getNextTextureRegister() const
{
	if (m_regit_tex != m_reg_tex.end())
		return *m_regit_tex++;

	return 0;
}


Material::Texture::Register* Material::getFirstTextureRegister() const
{
	m_regit_tex = m_reg_tex.begin();

	return getNextTextureRegister();
}


Material::EnvMat4::Register* Material::getNextMat4Register() const
{
	if (m_regit_mat4 != m_reg_mat4.end())
		return *m_regit_mat4++;

	return 0;
}


Material::EnvMat4::Register* Material::getFirstMat4Register() const
{
	m_regit_mat4 = m_reg_mat4.begin();

	return getNextMat4Register();
}


Material::EnvMat4v::Register* Material::getNextMat4vRegister() const
{
	if (m_regit_mat4v != m_reg_mat4v.end())
		return *m_regit_mat4v++;

	return 0;
}


Material::EnvMat4v::Register* Material::getFirstMat4vRegister() const
{
	m_regit_mat4v = m_reg_mat4v.begin();

	return getNextMat4vRegister();
}


Material::EnvVec4::Register* Material::getNextVec4Register() const
{
	if (m_regit_vec4 != m_reg_vec4.end())
		return *m_regit_vec4++;

	return 0;
}


Material::EnvVec4::Register* Material::getFirstVec4Register() const
{
	m_regit_vec4 = m_reg_vec4.begin();

	return getNextVec4Register();
}


Material::EnvVec4v::Register* Material::getNextVec4vRegister() const
{
	if (m_regit_vec4v != m_reg_vec4v.end())
		return *m_regit_vec4v++;

	return 0;
}


Material::EnvVec4v::Register* Material::getFirstVec4vRegister() const
{
	m_regit_vec4v = m_reg_vec4v.begin();

	return getNextVec4vRegister();
}


Material::Pass::Pass()
: m_parent(0)
, m_blend_src(BF_ONE)
, m_blend_dst(BF_ZERO)
{
	invalidate();
}


Material::Pass::Pass(const Pass& src)
: m_parent(0)
, m_name_vprog(src.m_name_vprog)
, m_name_fprog(src.m_name_fprog)
, m_blend_src(src.m_blend_src)
, m_blend_dst(src.m_blend_dst)
{
	invalidate();
}


Material::Pass& Material::Pass::operator =(const Material::Pass& src)
{
	this->~Pass();

	::new (this) Pass(src);

	return *this;
}


Material::Pass::~Pass()
{}


bool Material::Pass::save(FILE* f)
{
	return false;
}


bool Material::Pass::load(FILE* f)
{
	return false;
}


bool Material::Pass::load(
	const std::string& name,
	std::string& content)
{
	const unsigned buffer_len = 8192;
	static char buffer[buffer_len];

	bool success = false;

	FILE *f = fopen(name.c_str(), "rb");

	if (f)
	{
		const size_t read_count = fread(buffer, sizeof(buffer[0]), buffer_len - 1, f);

		if (feof(f) && !ferror(f))
		{
			buffer[read_count] = '\0';
			content = buffer;
			success = true;
		}

		fclose(f);
	}

	if (!f || !success)
		fprintf(stderr, "error reading file '%s'\n", name.c_str());

	return success;
}


bool Material::Pass::validate()
{
	if (!m_parent)
		return false;

	if (m_prog)
		return true;

	Program::Register* reg_prog = m_parent->getProgramRegister();
	assert(reg_prog);

	if (!createVertexProgram(reg_prog, m_name_vprog))
		return false;

	if (!createFragmentProgram(reg_prog, m_name_fprog))
		return false;

	LinkedProgram::Register* reg_link = m_parent->getLinkedProgramRegister();
	assert(reg_link);

	std::string name_linked;

	if (!createLinkedProgram(reg_link, name_linked, m_prog, reg_prog, m_name_vprog, m_name_fprog))
	{
		fprintf(stderr, "failed setting up linked program '%s'\n", name_linked.c_str());
		return false;
	}

	bool failure = false;

	m_uni_tex.clear();
	m_uni_vec4.clear();
	m_uni_vec4v.clear();
	m_uni_mat4.clear();
	m_uni_mat4v.clear();

	GLuint num_uniforms = 0;
	glGetProgramiv(m_prog, GL_ACTIVE_UNIFORMS, (GLint*) &num_uniforms);

	for (GLuint i = 0; i < num_uniforms; ++i)
	{
		GLchar name[256];
 		GLenum type;
	 	GLuint size;

		glGetActiveUniform(m_prog, i, sizeof(name) / sizeof(name[0]) - 1, 0, (GLint*) &size, &type, name);

		if (!strncmp(name, "gl_", 3))
			continue;

		const Texture::Register* reg_tex;
		const EnvVec4::Register* reg_vec4;
		const EnvVec4v::Register* reg_vec4v;
		const EnvMat4::Register* reg_mat4;
		const EnvMat4v::Register* reg_mat4v;

		switch (type)
		{
		case GL_SAMPLER_1D:
		case GL_SAMPLER_2D:
		case GL_SAMPLER_3D:
		case GL_SAMPLER_CUBE:

			for (reg_tex = m_parent->getFirstTextureRegister(); reg_tex != 0; reg_tex = m_parent->getNextTextureRegister())
			{
				const Texture* match = reg_tex->seek(name);

				if (match)
				{
					if (match->target == GL_TEXTURE_1D && type == GL_SAMPLER_1D ||
						match->target == GL_TEXTURE_2D && type == GL_SAMPLER_2D ||
						match->target == GL_TEXTURE_3D && type == GL_SAMPLER_3D ||
						match->target == GL_TEXTURE_CUBE_MAP && type == GL_SAMPLER_CUBE)
					{
						const GLint uniform = glGetUniformLocation(m_prog, name);
						assert(uniform != -1);

						m_uni_tex.push_back(std::pair< GLint, const Texture* >(uniform, match));
					}
					else
					{
						fprintf(stderr, "program '%s': sampler parameter mismatch '%s'\n", name_linked.c_str(), name);
						failure = true;
					}

					break;
				}
			}

			if (!reg_tex)
			{
				fprintf(stderr, "program '%s': missing sampler parameter '%s'\n", name_linked.c_str(), name);
				failure = true;
			}
			break;

		case GL_FLOAT_VEC4:

			if (size == 1)
			{
				for (reg_vec4 = m_parent->getFirstVec4Register(); reg_vec4 != 0; reg_vec4 = m_parent->getNextVec4Register())
				{
					const EnvVec4* match = reg_vec4->seek(name);

					if (match)
					{
						const GLint uniform = glGetUniformLocation(m_prog, name);
						assert(uniform != -1);

						m_uni_vec4.push_back(std::pair< GLint, const EnvVec4* >(uniform, match));

						break;
					}
				}

				if (!reg_vec4)
				{
					fprintf(stderr, "program '%s': missing vec4 parameter '%s'\n", name_linked.c_str(), name);
					failure = true;
				}
			}
			else
			{
				for (reg_vec4v = m_parent->getFirstVec4vRegister(); reg_vec4v != 0; reg_vec4v = m_parent->getNextVec4vRegister())
				{
					const EnvVec4v* match = reg_vec4v->seek(name);

					if (match)
					{
						const GLint uniform = glGetUniformLocation(m_prog, name);
						assert(uniform != -1);

						m_uni_vec4v.push_back(std::pair< std::pair< GLint, GLuint >, const EnvVec4v* >(std::pair< GLint, GLuint >(uniform, size), match));

						break;
					}
				}

				if (!reg_vec4v)
				{
					fprintf(stderr, "program '%s': missing vec4 parameter '%s'\n", name_linked.c_str(), name);
					failure = true;
				}
			}
			break;

		case GL_FLOAT_MAT4:

			if (size == 1)
			{
				for (reg_mat4 = m_parent->getFirstMat4Register(); reg_mat4 != 0; reg_mat4 = m_parent->getNextMat4Register())
				{
					const EnvMat4* match = reg_mat4->seek(name);

					if (match)
					{
						const GLint uniform = glGetUniformLocation(m_prog, name);
						assert(uniform != -1);

						m_uni_mat4.push_back(std::pair< GLint, const EnvMat4* >(uniform, match));

						break;
					}
				}

				if (!reg_mat4)
				{
					fprintf(stderr, "program '%s': missing mat4 parameter '%s'\n", name_linked.c_str(), name);
					failure = true;
				}
			}
			else
			{
				for (reg_mat4v = m_parent->getFirstMat4vRegister(); reg_mat4v != 0; reg_mat4v = m_parent->getNextMat4vRegister())
				{
					const EnvMat4v* match = reg_mat4v->seek(name);

					if (match)
					{
						const GLint uniform = glGetUniformLocation(m_prog, name);
						assert(uniform != -1);

						m_uni_mat4v.push_back(std::pair< std::pair< GLint, GLuint >, const EnvMat4v* >(std::pair< GLint, GLuint >(uniform, size), match));

						break;
					}
				}

				if (!reg_mat4v)
				{
					fprintf(stderr, "program '%s': missing mat4 parameter '%s'\n", name_linked.c_str(), name);
					failure = true;
				}
			}
			break;

		default:

			fprintf(stderr, "program '%s': unsupported type of parameter '%s'\n", name_linked.c_str(), name);
			failure = true;

			break;
		}
	}

	if (failure)
		m_prog = 0;

	return !failure;
}


void Material::Pass::actuate()
{
	if (!m_prog)
		return;

	glUseProgram(m_prog);

	GLuint tui = 0;
	for (std::vector< std::pair< GLint, const Texture* > >::const_iterator it = m_uni_tex.begin();
		it != m_uni_tex.end(); ++it, ++tui)
	{
		const Texture* entry = it->second;

		glActiveTexture(GL_TEXTURE0 + tui);
		glBindTexture(entry->target, entry->name);

		glUniform1i(it->first, tui);
	}

	for (std::vector< std::pair< GLint, const EnvVec4* > >::const_iterator it = m_uni_vec4.begin();
		it != m_uni_vec4.end(); ++it)
	{
		const EnvVec4* entry = it->second;

		glUniform4fv(it->first, GLsizei(1), &(const float&) entry->datum);
	}

	for (std::vector< std::pair< std::pair< GLint, GLuint >, const EnvVec4v* > >::const_iterator it = m_uni_vec4v.begin();
		it != m_uni_vec4v.end(); ++it)
	{
		const EnvVec4v* entry = it->second;
		const GLuint corr_count = std::min(it->first.second, entry->count);

		glUniform4fv(it->first.first, corr_count, &(const float&) entry->array[0]);
	}

	for (std::vector< std::pair< GLint, const EnvMat4* > >::const_iterator it = m_uni_mat4.begin();
		it != m_uni_mat4.end(); ++it)
	{
		const EnvMat4* entry = it->second;

		glUniformMatrix4fv(it->first, GLsizei(1), GL_TRUE, entry->datum[0]);
	}

	for (std::vector< std::pair< std::pair< GLint, GLuint >, const EnvMat4v* > >::const_iterator it = m_uni_mat4v.begin();
		it != m_uni_mat4v.end(); ++it)
	{
		const EnvMat4v* entry = it->second;
		const GLuint corr_count = std::min(it->first.second, entry->count);

		glUniformMatrix4fv(it->first.first, corr_count, GL_TRUE, entry->array[0][0]);
	}

	// set blending for this pass
	if (m_blend_src != BF_ONE || m_blend_dst != BF_ZERO)
	{
		GLenum sfactor = GL_ONE;

		switch (m_blend_src)
		{
		case BF_ZERO:
			sfactor	= GL_ZERO;
			break;
		case BF_OTHER_COLOR:
			sfactor = GL_DST_COLOR;
			break;
		case BF_ONE_MINUS_OTHER_COLOR:
			sfactor = GL_ONE_MINUS_DST_COLOR;
			break;
		case BF_SRC_ALPHA:
			sfactor = GL_SRC_ALPHA;
			break;
		case BF_ONE_MINUS_SRC_ALPHA:
			sfactor = GL_ONE_MINUS_SRC_ALPHA;
			break;
		case BF_DST_ALPHA:
			sfactor = GL_DST_ALPHA;
			break;
		case BF_ONE_MINUS_DST_ALPHA:
			sfactor = GL_ONE_MINUS_DST_ALPHA;
			break;
		}

		GLenum dfactor = GL_ZERO;

		switch (m_blend_dst)
		{
		case BF_ONE:
			dfactor	= GL_ONE;
			break;
		case BF_OTHER_COLOR:
			dfactor = GL_SRC_COLOR;
			break;
		case BF_ONE_MINUS_OTHER_COLOR:
			dfactor = GL_ONE_MINUS_SRC_COLOR;
			break;
		case BF_SRC_ALPHA:
			dfactor = GL_SRC_ALPHA;
			break;
		case BF_ONE_MINUS_SRC_ALPHA:
			dfactor = GL_ONE_MINUS_SRC_ALPHA;
			break;
		case BF_DST_ALPHA:
			dfactor = GL_DST_ALPHA;
			break;
		case BF_ONE_MINUS_DST_ALPHA:
			dfactor = GL_ONE_MINUS_DST_ALPHA;
			break;
		}

		glBlendEquation(GL_FUNC_ADD);
		glBlendFunc(sfactor, dfactor);
		glEnable(GL_BLEND);
	}
	else
		glDisable(GL_BLEND);

	if (reportGLError(stderr))
		fprintf(stderr, "pass setup failed; gl state undefined\n");
}


void Material::Pass::setBlendFactors(const BlendFactor src, const BlendFactor dst)
{
	m_blend_src = src;
	m_blend_dst = dst;
}


void Material::Pass::getBlendFactors(BlendFactor& src, BlendFactor& dst) const
{
	src = m_blend_src;
	dst = m_blend_dst;
}


bool Material::Pass::isBlending() const
{
	return
		m_blend_dst != BF_ZERO ||
		m_blend_src == BF_OTHER_COLOR ||
		m_blend_src == BF_ONE_MINUS_OTHER_COLOR ||
		m_blend_src == BF_DST_ALPHA ||
		m_blend_src == BF_ONE_MINUS_DST_ALPHA;
}


void Material::Pass::setNameVertProg(const std::string& name)
{
	m_name_vprog = name;

	invalidate();
}


const std::string& Material::Pass::getNameVertProg() const
{
	return m_name_vprog;
}


void Material::Pass::setNameFragProg(const std::string& name)
{
	m_name_fprog = name;

	invalidate();
}


const std::string& Material::Pass::getNameFragProg() const
{
	return m_name_fprog;
}


Material::Material()
: m_reg_prog(&Program::s_default_register)
, m_reg_linked(&LinkedProgram::s_default_register)
{
	m_reg_tex.insert(m_reg_tex.end(), &Texture::s_default_register);
	m_reg_mat4.insert(m_reg_mat4.end(), &EnvMat4::s_default_register);
	m_reg_mat4v.insert(m_reg_mat4v.end(), &EnvMat4v::s_default_register);
	m_reg_vec4.insert(m_reg_vec4.end(), &EnvVec4::s_default_register);
	m_reg_vec4v.insert(m_reg_vec4v.end(), &EnvVec4v::s_default_register);

	m_regit_tex = m_reg_tex.end();
	m_regit_mat4 = m_reg_mat4.end();
	m_regit_mat4v = m_reg_mat4v.end();
	m_regit_vec4 = m_reg_vec4.end();
	m_regit_vec4v = m_reg_vec4v.end();
}


Material::Material(const Material& src)
: m_reg_prog(src.m_reg_prog)
, m_reg_linked(src.m_reg_linked)
, m_reg_tex(src.m_reg_tex)
, m_reg_mat4(src.m_reg_mat4)
, m_reg_mat4v(src.m_reg_mat4v)
, m_reg_vec4(src.m_reg_vec4)
, m_reg_vec4v(src.m_reg_vec4v)
, m_passes(src.m_passes)
{
	for (unsigned i = 0; i < m_passes.size(); ++i)
		m_passes[i].setParent(this);

	m_regit_tex = m_reg_tex.end();
	m_regit_mat4 = m_reg_mat4.end();
	m_regit_mat4v = m_reg_mat4v.end();
	m_regit_vec4 = m_reg_vec4.end();
	m_regit_vec4v = m_reg_vec4v.end();
}


Material& Material::operator =(const Material& src)
{
	this->~Material();

	::new (this) Material(src);

	return *this;
}


Material::~Material()
{}


bool Material::save(FILE* f)
{
	return false;
}


bool Material::load(FILE* f)
{
	return false;
}


bool Material::save(const std::string& fname)
{
	return false;
}


bool Material::load(const std::string& fname)
{
	return false;
}


void Material::invalidate()
{
	for (std::vector< Pass >::iterator it = m_passes.begin(); it != m_passes.end(); ++it)
		it->invalidate();
}


void Material::setProgramRegister(
	Material::Program::Register* r)
{
	assert(r);

	m_reg_prog = r;

	invalidate();
}


Material::Program::Register* Material::getProgramRegister() const
{
	return m_reg_prog;
}


void Material::setLinkedProgramRegister(
	Material::LinkedProgram::Register* r)
{
	assert(r);

	m_reg_linked = r;

	invalidate();
}


Material::LinkedProgram::Register* Material::getLinkedProgramRegister() const
{
	return m_reg_linked;
}


void Material::insertTextureRegister(
	Material::Texture::Register* r,
	unsigned position)
{
	assert(r);

	std::list< Texture::Register* >::iterator it = m_reg_tex.begin();
	for (; it != m_reg_tex.end() && position; ++it, --position);

	m_reg_tex.insert(it, r);
	m_regit_tex = m_reg_tex.end();

	invalidate();
}


void Material::removeTextureRegister(
	Material::Texture::Register* r)
{
	assert(r);

	m_reg_tex.remove(r);

	invalidate();
}


void Material::insertMat4Register(
	Material::EnvMat4::Register* r,
	unsigned position)
{
	assert(r);

	std::list< EnvMat4::Register* >::iterator it = m_reg_mat4.begin();
	for (; it != m_reg_mat4.end() && position; ++it, --position);

	m_reg_mat4.insert(it, r);
	m_regit_mat4 = m_reg_mat4.end();

	invalidate();
}


void Material::removeMat4Register(
	Material::EnvMat4::Register* r)
{
	assert(r);

	m_reg_mat4.remove(r);

	invalidate();
}


void Material::insertMat4vRegister(
	Material::EnvMat4v::Register* r,
	unsigned position)
{
	assert(r);

	std::list< EnvMat4v::Register* >::iterator it = m_reg_mat4v.begin();
	for (; it != m_reg_mat4v.end() && position; ++it, --position);

	m_reg_mat4v.insert(it, r);
	m_regit_mat4v = m_reg_mat4v.end();

	invalidate();
}


void Material::removeMat4vRegister(
	Material::EnvMat4v::Register* r)
{
	assert(r);

	m_reg_mat4v.remove(r);

	invalidate();
}


void Material::insertVec4Register(
	Material::EnvVec4::Register* r,
	unsigned position)
{
	assert(r);

	std::list< EnvVec4::Register* >::iterator it = m_reg_vec4.begin();
	for (; it != m_reg_vec4.end() && position; ++it, --position);

	m_reg_vec4.insert(it, r);
	m_regit_vec4 = m_reg_vec4.end();

	invalidate();
}


void Material::removeVec4Register(
	Material::EnvVec4::Register* r)
{
	assert(r);

	m_reg_vec4.remove(r);

	invalidate();
}


void Material::insertVec4vRegister(
	Material::EnvVec4v::Register* r,
	unsigned position)
{
	assert(r);

	std::list< EnvVec4v::Register* >::iterator it = m_reg_vec4v.begin();
	for (; it != m_reg_vec4v.end() && position; ++it, --position);

	m_reg_vec4v.insert(it, r);
	m_regit_vec4v = m_reg_vec4v.end();

	invalidate();
}


void Material::removeVec4vRegister(
	Material::EnvVec4v::Register* r)
{
	assert(r);

	m_reg_vec4v.remove(r);

	invalidate();
}


void Material::setNumPasses(const unsigned numPasses)
{
	const size_t old_size = m_passes.size();

	m_passes.resize(numPasses);

	for (unsigned i = old_size; i < numPasses; ++i)
		m_passes[i].setParent(this);
}


Material::Pass& Material::getPass(const unsigned pass_i)
{
	assert(pass_i < m_passes.size());

	return m_passes[pass_i];
}


bool Material::validate()
{
	bool res = true;

	std::vector< Pass >::iterator it = m_passes.begin();

	for (; it != m_passes.end(); ++it)
		res &= it->validate();

	return res;
}


void Material::actuate(const unsigned pass_i)
{
	getPass(pass_i).actuate();
}


void Material::prologue()
{
	const bool success = validate();
	assert(success);
}


void Material::epilogue()
{
	glUseProgram(0);
}


// next routine based on cubemap generation code by Paul Baker, paulsprojects.net

bool Material::Pass::createNormalizerCubemap(
	Texture::Register* reg_tex,
	const std::string& name,
	const unsigned resolution)
{
	assert(reg_tex);

	const Texture* match = reg_tex->seek(name);

	if (match && match->name)
		return true;

	if (!resolution || (resolution & (resolution - 1)))
		return false;

	GLuint tex;
	glGenTextures(1, &tex);

	glActiveTexture(GL_TEXTURE0);
	glBindTexture(GL_TEXTURE_CUBE_MAP, tex);

	GLubyte* const data = new GLubyte[3 * resolution * resolution];

	const float offset = .5f;
	const float half_res = resolution / 2;

	const vect3 scale_bias(.5f, .5f, .5f);

	// positive x
	for (unsigned y = 0; y < resolution; ++y)
	{
		for (unsigned z = 0; z < resolution; ++z)
		{
			vect3 v(
				half_res,
				half_res - y - offset,
				half_res - z - offset);

			v.normalise();
			v.mul(scale_bias);
			v.add(scale_bias);
			v.mul((1 << sizeof(data[0]) * 8) - 1);

			data[(y * resolution + z) * 3 + 0] = (GLubyte) toIntRound(v.get(0));
			data[(y * resolution + z) * 3 + 1] = (GLubyte) toIntRound(v.get(1));
			data[(y * resolution + z) * 3 + 2] = (GLubyte) toIntRound(v.get(2));
		}
	}

	glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, GL_RGBA8,
		resolution, resolution, 0, GL_RGB, GL_UNSIGNED_BYTE, data);

	// negative x
	for (unsigned y = 0; y < resolution; ++y)
	{
		for (unsigned z = 0; z < resolution; ++z)
		{
			vect3 v(
				-half_res,
				 half_res - y - offset,
				-half_res + z + offset);

			v.normalise();
			v.mul(scale_bias);
			v.add(scale_bias);
			v.mul((1 << sizeof(data[0]) * 8) - 1);

			data[(y * resolution + z) * 3 + 0] = (GLubyte) toIntRound(v.get(0));
			data[(y * resolution + z) * 3 + 1] = (GLubyte) toIntRound(v.get(1));
			data[(y * resolution + z) * 3 + 2] = (GLubyte) toIntRound(v.get(2));
		}
	}

	glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, GL_RGBA8,
		resolution, resolution, 0, GL_RGB, GL_UNSIGNED_BYTE, data);

	// positive y
	for (unsigned z = 0; z < resolution; ++z)
	{
		for (unsigned x = 0; x < resolution; ++x)
		{
			vect3 v(
				-half_res + x + offset,
				 half_res,
				-half_res + z + offset);

			v.normalise();
			v.mul(scale_bias);
			v.add(scale_bias);
			v.mul((1 << sizeof(data[0]) * 8) - 1);

			data[(z * resolution + x) * 3 + 0] = (GLubyte) toIntRound(v.get(0));
			data[(z * resolution + x) * 3 + 1] = (GLubyte) toIntRound(v.get(1));
			data[(z * resolution + x) * 3 + 2] = (GLubyte) toIntRound(v.get(2));
		}
	}

	glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, GL_RGBA8,
		resolution, resolution, 0, GL_RGB, GL_UNSIGNED_BYTE, data);

	// negative y
	for (unsigned z = 0; z < resolution; ++z)
	{
		for (unsigned x = 0; x < resolution; ++x)
		{
			vect3 v(
				-half_res + x + offset,
				-half_res,
				 half_res - z - offset);

			v.normalise();
			v.mul(scale_bias);
			v.add(scale_bias);
			v.mul((1 << sizeof(data[0]) * 8) - 1);

			data[(z * resolution + x) * 3 + 0] = (GLubyte) toIntRound(v.get(0));
			data[(z * resolution + x) * 3 + 1] = (GLubyte) toIntRound(v.get(1));
			data[(z * resolution + x) * 3 + 2] = (GLubyte) toIntRound(v.get(2));
		}
	}

	glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, GL_RGBA8,
		resolution, resolution, 0, GL_RGB, GL_UNSIGNED_BYTE, data);

	// positive z
	for (unsigned y = 0; y < resolution; ++y)
	{
		for (unsigned x = 0; x < resolution; ++x)
		{
			vect3 v(
					-half_res + x + offset,
					 half_res - y - offset,
					 half_res);

			v.normalise();
			v.mul(scale_bias);
			v.add(scale_bias);
			v.mul((1 << sizeof(data[0]) * 8) - 1);

			data[(y * resolution + x) * 3 + 0] = (GLubyte) toIntRound(v.get(0));
			data[(y * resolution + x) * 3 + 1] = (GLubyte) toIntRound(v.get(1));
			data[(y * resolution + x) * 3 + 2] = (GLubyte) toIntRound(v.get(2));
		}
	}

	glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, GL_RGBA8,
		resolution, resolution, 0, GL_RGB, GL_UNSIGNED_BYTE, data);

	// negative z
	for (unsigned y = 0; y < resolution; ++y)
	{
		for (unsigned x = 0; x < resolution; ++x)
		{
			vect3 v(
				 half_res - x - offset,
				 half_res - y - offset,
				-half_res);

			v.normalise();
			v.mul(scale_bias);
			v.add(scale_bias);
			v.mul((1 << sizeof(data[0]) * 8) - 1);

			data[(y * resolution + x) * 3 + 0] = (GLubyte) toIntRound(v.get(0));
			data[(y * resolution + x) * 3 + 1] = (GLubyte) toIntRound(v.get(1));
			data[(y * resolution + x) * 3 + 2] = (GLubyte) toIntRound(v.get(2));
		}
	}

	glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, GL_RGBA8,
		resolution, resolution, 0, GL_RGB, GL_UNSIGNED_BYTE, data);

	glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
	glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

	glBindTexture(GL_TEXTURE_CUBE_MAP, 0);

	delete [] data;

	const bool success = !reportGLError(stderr);

	if (success)
	{
		Texture* reg = reg_tex->insert(name);
		reg->name = tex;
		reg->target = GL_TEXTURE_CUBE_MAP;	
	}

	return success;
}


bool Material::Pass::createExponentiationMap(
	Texture::Register* reg_tex,
	const std::string& name,
	const unsigned exp_res,
	const unsigned base_res)
{
	assert(reg_tex);

	const Texture* match = reg_tex->seek(name);

	if (match && match->name)
		return true;

	if (exp_res < 1 || (exp_res & (exp_res - 1)) ||
		base_res < 2 || (base_res & (base_res - 1)))
	{
		return false;
	}

	GLuint tex;
	glGenTextures(1, &tex);

	glActiveTexture(GL_TEXTURE0);
	glBindTexture(GL_TEXTURE_2D, tex);

	GLushort* const data = new GLushort[exp_res * base_res];

	for (unsigned y = 0; y < exp_res; ++y)
	{
		for (unsigned x = 0; x < base_res; ++x)
		{
			// base spans [0, 1], exp spans [1, exp_res]
			const float base = (float)x / (base_res - 1);
			const float exp = (float)(y + 1);

			data[y * base_res + x] = (GLushort) toIntRound(GLushort(-1) * powf(base, exp));
		}
	}

	glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE16,
		base_res, exp_res, 0, GL_LUMINANCE, GL_UNSIGNED_SHORT, data);

	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

	glBindTexture(GL_TEXTURE_2D, 0);

	delete [] data;

	const bool success = !reportGLError(stderr);

	if (success)
	{
		Texture* reg = reg_tex->insert(name);
		reg->name = tex;
		reg->target = GL_TEXTURE_2D;
	}

	return success;
}


bool Material::Pass::createTexture2DFromFile(
	Texture::Register* reg_tex,
	const std::string& name,
	const std::string& filename)
{
	assert(reg_tex);

	const Texture* match = reg_tex->seek(name);

	if (match && match->name)
		return true;

	typedef rend::PixelRGB888 pix;

	const unsigned bpp = pix::NUMBYTES;

	pix* data = 0;

	unsigned tex_w = 0;
	unsigned tex_h = 0;

	bool success = false;
	FILE* file = fopen(filename.c_str(), "rb");

	if (file)
	{
		uint32 dim[2];

		if (2 == fread(dim, sizeof(dim[0]), 2, file))
		{
			swapEndiannessIfHostBig(dim[0]);
			swapEndiannessIfHostBig(dim[1]);

			if (dim[0] && 0 == (dim[0] & dim[0] - 1) &&
				dim[1] && 0 == (dim[1] & dim[1] - 1))
			{
				tex_w = dim[0];
				tex_h = dim[1];

				const unsigned tex_size = tex_w * tex_h * bpp;

				data = new pix[(tex_size + sizeof(pix) - 1) / sizeof(pix)];

				fprintf(stdout, "texture bitmap '%s' %d x %d x %d bpp, %d bytes\n",
					filename.c_str(), tex_w, tex_h, bpp, tex_size);

				const unsigned read = fread(data, 1, tex_size, file);

				if (read == tex_size)
					success = true;
				else
					fprintf(stderr, "read %u bytes from bitmap file (expected %u).\n", read, tex_size);
			}
			else
				fprintf(stderr, "invalid bitmap dimensions in bitmap file '%s'.\n", filename.c_str());
		}

		fclose(file);
	}
	else
		fprintf(stderr, "failure opening texture bitmap file '%s'.\n", filename.c_str());

	if (!success)
	{
		if (!tex_w)
			tex_w = 64;

		if (!tex_h)
			tex_h = 64;

		if (!data)
		{
			const unsigned tex_size = tex_w * tex_h * bpp;
			data = new pix[(tex_size + sizeof(pix) - 1) / sizeof(pix)];
		}

		const pix pixA(255U, 128U, 128U);
		const pix pixB(128U, 128U, 255U);

		for (unsigned y = 0; y < tex_h; ++y)
			for (unsigned x = 0; x < tex_w; ++x)
			{
				*reinterpret_cast< pix* >(
					reinterpret_cast< int8 (*)[bpp] >(data) + y * tex_w + x) = (y & 8) ^ (x & 8)
						? pixB
						: pixA;
			}
	}

	GLuint tex = 0;
	glGenTextures(1, &tex);

	glActiveTexture(GL_TEXTURE0);
	glBindTexture(GL_TEXTURE_2D, tex);

	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
	glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_TRUE);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);

	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, tex_w, tex_h, 0, GL_RGB, GL_UNSIGNED_BYTE, data);

	glBindTexture(GL_TEXTURE_2D, 0);

	delete [] data;

	success = !reportGLError(stderr);

	if (success)
	{
		Texture* reg = reg_tex->insert(name);
		reg->name = tex;
		reg->target = GL_TEXTURE_2D;
	}
	else
		fprintf(stderr, "failed to create a 2D texture '%s'.\n", name.c_str());

	return success;
}


bool Material::Pass::createVertexProgram(
	Program::Register* reg_prog,
	const std::string& name)
{
	assert(reg_prog);

	const Program* match = reg_prog->seek(name);

	if (match && match->name)
		return true;

	std::string source;

	if (!load(name, source))
	{
		fprintf(stderr, "failure loading vertex program '%s'\n", name.c_str());
		return false;
	}

	const GLuint prog = glCreateShader(GL_VERTEX_SHADER);
	assert(prog);

	const char* src_ref = source.c_str();
	const GLint src_len = source.length();

	glShaderSource(prog, 1, &src_ref, &src_len);
	glCompileShader(prog);

	GLint success;
	glGetShaderiv(prog, GL_COMPILE_STATUS, &success);

	if (GL_FALSE == success)
	{
		const GLsizei log_max_len = 2048;
		static GLchar log[log_max_len + 1];
		GLsizei len = 0;

		glGetShaderInfoLog(prog, log_max_len, &len, log);
		glDeleteShader(prog);

		log[len] = '\0';
		fprintf(stderr, "%s", log);

		return false;
	}

	Program* reg = reg_prog->insert(name);
	reg->name = prog;

	printf("shader program id 0x%08x '%s'\n", prog, name.c_str());

	return true;
}


bool Material::Pass::createFragmentProgram(
	Program::Register* reg_prog,
	const std::string& name)
{
	assert(reg_prog);

	const Program* match = reg_prog->seek(name);

	if (match && match->name)
		return true;

	std::string source;

	if (!load(name, source))
	{
		fprintf(stderr, "failure loading fragment program '%s'\n", name.c_str());
		return false;
	}

	const GLuint prog = glCreateShader(GL_FRAGMENT_SHADER);
	assert(prog);

	const char* src_ref = source.c_str();
	const GLint src_len = source.length();

	glShaderSource(prog, 1, &src_ref, &src_len);
	glCompileShader(prog);

	GLint success;
	glGetShaderiv(prog, GL_COMPILE_STATUS, &success);

	if (GL_FALSE == success)
	{
		const GLsizei log_max_len = 2048;
		static GLchar log[log_max_len + 1];
		GLsizei len = 0;

		glGetShaderInfoLog(prog, log_max_len, &len, log);
		glDeleteShader(prog);

		log[len] = '\0';
		fprintf(stderr, "%s", log);

		return false;
	}

	Program* reg = reg_prog->insert(name);
	reg->name = prog;

	printf("shader program id 0x%08x '%s'\n", prog, name.c_str());

	return true;
}


bool Material::Pass::createLinkedProgram(
	LinkedProgram::Register* reg_link,
	std::string& name,
	GLuint& native_name,
	const Program::Register* reg_prog,
	const std::string& name_vertex_program,
	const std::string& name_fragment_program)
{
	assert(reg_link);
	assert(reg_prog);

	name = name_vertex_program + " " + name_fragment_program;

	const LinkedProgram* match = reg_link->seek(name);

	if (match && match->name)
	{
		native_name = match->name;
		return true;
	}

	const Program* vertex_program = reg_prog->seek(name_vertex_program);

	if (!vertex_program || !vertex_program->name)
		return false;

	const Program* fragment_program = reg_prog->seek(name_fragment_program);

	if (!fragment_program || !fragment_program->name)
		return false;

	const GLuint prog = glCreateProgram();
	assert(prog);

	glAttachShader(prog, vertex_program->name);
	glAttachShader(prog, fragment_program->name);

	glLinkProgram(prog);

	GLint success;
	glGetProgramiv(prog, GL_LINK_STATUS, &success);

	if (GL_FALSE == success)
	{
		const GLsizei log_max_len = 2048;
		static GLchar log[log_max_len + 1];
		GLsizei len = 0;

		glGetProgramInfoLog(prog, log_max_len, &len, log);
		glDeleteProgram(prog);

		log[len] = '\0';
		fprintf(stderr, "%s", log);

		return false;
	}

	LinkedProgram* reg = reg_link->insert(name);
	reg->name = native_name = prog;

	printf("linked program id 0x%08x '%s'\n", prog, name.c_str());

	return true;
}

} // namespace rend
